package com.hmall.item;

import cn.hutool.core.bean.BeanUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.DistanceUnit;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.FunctionBoostMode;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.hmall.item.domain.po.Item;
import com.hmall.item.domain.po.ItemDoc;
import com.hmall.item.service.IItemService;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * TODO
 *
 * @Author mr.wu
 * @Data #{DATE} #{TIME}
 */
@SpringBootTest
@Slf4j
public class IndexText {
    private ElasticsearchClient esClient;
    private ElasticsearchTransport transport;

    @BeforeEach
    public void init() {
        RestClient restClient = RestClient.builder(
                new HttpHost("192.168.101.68", 9200)).build();

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        transport = new RestClientTransport(
                restClient, new JacksonJsonpMapper(objectMapper));

        esClient = new ElasticsearchClient(transport);

    }

    @AfterEach
    public void destroy() throws IOException {
        transport.close();
    }

    @Autowired
    private IItemService itemService;

    @Test
    public void AddESDocumentTransport() throws IOException {
        //查询商品
        Item item = itemService.getById(317578L);

        //转换类型（表实体->ES实体）
        ItemDoc itemDoc = BeanUtil.toBean(item, ItemDoc.class);

        //构建索引请求实例
//        IndexRequest indexRequest = new IndexRequest.Builder<ItemDoc>()
//                .index("items")
//                .id(itemDoc.getId())
//                .document(itemDoc)
//                .build();
        //执行新增商品请求
//        IndexResponse response = esClient.index(indexRequest);

        List<ItemDoc.Attr> specList = Arrays.asList(
                ItemDoc.Attr.builder().name("颜色").value("绿色").build(),
                ItemDoc.Attr.builder().name("尺寸").value("22寸").build()
        );
        itemDoc.setAttrList(specList);
        //执行新增商品请求
        IndexResponse response = esClient.index(i ->
                i.index("items")
                        .id(itemDoc.getId())
                        .document(itemDoc)
        );

        //4. 处理响应结果
        String result = response.result().jsonValue();
        Assertions.assertTrue(result.equals("created") || result.equals("updated"), "新增商品数据到ES失败");
    }


    @DisplayName("批量新增文档")
    @Test
    public void batchAddDocument() throws IOException {
        //获取总记录数
        Integer itemTotal = itemService.lambdaQuery().count();
        //计算总页数
        double ceil = Math.ceil(itemTotal / 1000d);
        int pageNumTotal = (int) ceil;
        log.info("总记录数：{}，计算结果：{}，总页数：{}", itemTotal, ceil, pageNumTotal);

        int pageNum = 1;

        while (pageNum <= pageNumTotal) {
            //分页查询1000条文档
            Page<Item> page = itemService.lambdaQuery().page(Page.of(pageNum, 1000));
            List<Item> itemList = page.getRecords();

            //类型转换
            List<ItemDoc> itemDocList = BeanUtil.copyToList(itemList, ItemDoc.class);



            //批量添加索引请求实例
            BulkRequest.Builder bulkBuilder = new BulkRequest.Builder();
            itemDocList.forEach(x -> {

                List<ItemDoc.Attr> specList = Arrays.asList(
                        ItemDoc.Attr.builder().name("颜色").value("绿色").build(),
                        ItemDoc.Attr.builder().name("尺寸").value("22寸").build()
                );
                x.setAttrList(specList);

                String category = x.getCategory();
                String brand = x.getBrand();
                //将分类和品牌设置到建议字段
                x.setSuggestion(List.of(category,brand));

                bulkBuilder.operations(o -> o.index(
                        i -> i.index("items").id(x.getId()).document(x)
                ));
            });

            //构建批量请求实例
            BulkRequest bulkRequest = bulkBuilder.build();

            //执行批量新增商品请求
            BulkResponse response = esClient.bulk(bulkRequest);

            //处理响应结果
            long count = response.items().stream().filter(x -> x.result().equals("created") || x.result().equals("updated")).count();
            //Assertions.assertEquals(10,count, "批量新增商品到ES失败");
            log.info("导入成功第{}页，导入成功：{}条", pageNum, count);
            pageNum++;

        }
    }


    @Test
    @DisplayName("查询文档")
    public void selectDocument() throws IOException {
        GetResponse<ItemDoc> response = esClient.get(x -> x.index("items")
                        .id("317578")
                , ItemDoc.class);
        ItemDoc source = response.source();
        Assertions.assertEquals(28900,source.getPrice(),"查询文档失败");
    }


    @Test
    @DisplayName("更新文档")
    public void updateDocument() throws IOException {
        UpdateResponse<ItemDoc> response = esClient.update(u -> u.index("items")
                        .id("317578")
                        .doc(ItemDoc.builder().stock(1000).build())
                        .docAsUpsert(true)
                , ItemDoc.class);
        String result = response.result().jsonValue();
        Assertions.assertTrue(result.equals("created") || result.equals("updated"), "更新文档失败");
    }

    @Test
    @DisplayName("删除文档")
    public void deleteDocument() throws IOException {
        DeleteResponse response = esClient.delete(u -> u.index("items")
                .id("317578")
        );
        String result = response.result().jsonValue();
        Assertions.assertTrue(result.equals("deleted"), "删除失败");
    }


    @Test
    @DisplayName("term-精确词条查询")
    public void selectTerm() throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s -> s.index("items")
                        .trackTotalHits(t -> t.enabled(true))
                        .query(q -> q.term(t -> t.field("category").value("拉杆箱")))
                , ItemDoc.class);
        long total = response.hits().total().value();
        log.info("[term-精确词条查询]查询条数{}",total);
        response.hits().hits().forEach(x-> System.out.println(x.source()));
    }

    @Test
    @DisplayName("range-范围查询")
    public void selectRange() throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s -> s.index("items")
                        .trackTotalHits(t -> t.enabled(true))
                        .query(q -> q.range(r->r.field("price").gte(JsonData.of(100000)).lte(JsonData.of(300000))))
                , ItemDoc.class);
        long total = response.hits().total().value();
        log.info("[range-范围查询]查询条数{}",total);
        response.hits().hits().forEach(x-> System.out.println(x.source()));
    }


    @Test
    @DisplayName("match-全文检索查询")
    public void selectMatch() throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s -> s.index("items")
                        .trackTotalHits(t -> t.enabled(true))
                        .query(q -> q.match(m->m.field("name").query("拉杆箱")))
                , ItemDoc.class);
        long total = response.hits().total().value();
        log.info("[match-全文检索查询]查询条数{}",total);
        response.hits().hits().forEach(x-> System.out.println(x.source()));
    }

    @Test
    @DisplayName("multi_Match-多字段检索查询")
    public void selectMultiMatch() throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s -> s.index("items")
                        .trackTotalHits(t -> t.enabled(true))
                        .query(q -> q.multiMatch(m->m.fields("brand","category").query("诺基亚")))
                , ItemDoc.class);
        long total = response.hits().total().value();
        log.info("[multi_Match-多字段检索查询]查询条数{}",total);
        response.hits().hits().forEach(x-> System.out.println(x.source()));
    }

    @Test
    @DisplayName("sortPage-结果查询")
    public void selectSort() throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s -> s.index("items")
                        .trackTotalHits(t -> t.enabled(true))
                        .query(q -> q.term(t->t.field("category").value("拉杆箱")))
                        .sort(ss->ss.field(f->f.field("price").order(SortOrder.Desc)))
                        .from(0)
                        .size(20)
                , ItemDoc.class);
        long total = response.hits().total().value();
        log.info("[sortPage-结果查询]查询条数{}",total);
        response.hits().hits().forEach(x-> System.out.println(x.source()));
    }


    @Test
    @DisplayName("highlight-结果查询")
    public void selectHighlight() throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s -> s.index("items")
                        .trackTotalHits(t -> t.enabled(true))
                        .query(q -> q.match(m->m.field("name").query("梦幻蓝")))
                        .highlight(h->h.fields("name",hf->hf.preTags("<em>").postTags("</em>")))
                , ItemDoc.class);
        long total = response.hits().total().value();
        log.info("[highlight-结果查询]查询条数{}",total);
        response.hits().hits().forEach(x-> System.out.println(x.highlight().get("name").get(0)));

    }

    @Test
    @DisplayName("functionScore-算分查询")
    public void selectFunctionScore() throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s->s.index("items")
                        .query(q->q.functionScore(f->f.query(qq->qq.bool(b->b.must(m->
                                m.match(mm->mm.field("name").query("拉杆箱手机")))
                                .filter(ff->ff.term(t->t.field("brand").value("vivo")))
                        ))
                                        .functions(ff->ff.filter(fff->fff.range(r->r.field("price").lte(JsonData.of(1000))))
                                                .weight(20d)
                                        )
                                        .boostMode(FunctionBoostMode.Sum)

                        ))
                , ItemDoc.class);
        long total = response.hits().total().value();
        log.info("[functionScore-算分查询]查询条数{}",total);
        response.hits().hits().forEach(x-> System.out.println(x.source()));
    }


    @Test
    @DisplayName("geo_distance-地理位置圆形查询")
    public void selectGeoDistance() throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s->s.index("items")
                        .query(q->q.geoDistance(g->g.distance("5km").field("location")
                                .location(l->l.latlon(ll->ll.lat(34.55).lon(113.86)))))
                , ItemDoc.class);
        long total = response.hits().total().value();
        log.info("[geo_distance-地理位置圆形查询]查询条数{}",total);
        response.hits().hits().forEach(x-> System.out.println(x.source()));
    }

    @Test
    @DisplayName("geo_distance-地理位置圆形查询(带排序)")
    public void selectGeoDistanceSort() throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s->s.index("items")
                        .query(q->q.geoDistance(g->g.distance("5km").field("location")
                                .location(l->l.latlon(ll->ll.lat(34.55).lon(113.86)))))
                        .sort(ss->ss.geoDistance(g->g.order(SortOrder.Asc).field("location").location(l->l.latlon(ll->ll.lat(34.55).lon(113.86))).unit(DistanceUnit.Kilometers)))
                , ItemDoc.class);
        long total = response.hits().total().value();
        log.info("[geo_distance-地理位置圆形查询(带排序)]查询条数{}",total);
        response.hits().hits().forEach(x-> System.out.println(x.source()));
    }

    @Test
    @DisplayName("nested-规格查询")
    public void selectNested() throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s->s.index("items")
                        .query(q->q.nested(n->n.path("attr_list").query(qq->
                                qq.bool(b->b.should(ss->{
                                            ss.term(t->t.field("attr_list.name").value("尺寸"));
                                            ss.term(t->t.field("attr_list.name").value("颜色"));
                                            return ss;
                                        }
                                )))))
                        .size(0)
                        .aggregations("attr_list_agg", agg->agg.nested(n->n.path("attr_list"))
                                .aggregations("name_agg",aggr->aggr.terms(t->t.field("attr_list.name").size(10))
                                        .aggregations("value_agg", vagg->vagg.terms(tt->tt.field("attr_list.value").size(10)))
                                )
                        )

                , ItemDoc.class);
        long total = response.hits().total().value();
        log.info("[nested-规格查询]查询条数{}",total);
        Aggregate attrListAgg = response.aggregations().get("attr_list_agg");
        Aggregate nameAgg = attrListAgg.nested().aggregations().get("name_agg");
        List<StringTermsBucket> nameList = nameAgg.sterms().buckets().array();
        nameList.forEach(b->{
            System.out.println(b.key().stringValue());
            List<StringTermsBucket> valueList = b.aggregations().get("value_agg").sterms().buckets().array();
            valueList.forEach(c-> System.out.println(c.key().stringValue()));
                }
        ) ;

    }

   @Test
    public void suggestion() throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index("items")
                .suggest(s->s.text("la").suggesters("suggestion_suggest",
                        su->su.completion(c->c.field("suggestion").size(100).skipDuplicates(true
                        ))));
        SearchRequest request = builder.build();
        SearchResponse<ItemDoc> response = esClient.search(request, ItemDoc.class);
        List<String> list  =new ArrayList<>();
        response.suggest().get("suggestion_suggest").forEach(b->{
            b.completion().options().forEach(c->{
                list.add(c.text());
            });
        });
       System.out.println(list);
    }



}

